﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Collections;
using System.Data.SqlTypes;

using Noris.Schedule.Support;
using Noris.Schedule.Support.Core;
using Noris.Schedule.Support.Data;
using Noris.Schedule.Support.Sql;
using Noris.Schedule.Support.DataFace;
using Noris.Schedule.Support.Services;

using Noris.Schedule.Planning.DataFace;
using System.Drawing;

namespace Noris.Schedule.Planning.ProcessData
{
	#region CLASS AxisHeapCls : v sobě fyzicky uchovává data materiálové osy S a kapacitní úkoly, a vztahy mezi nimi.
	/// <summary>
	/// Třída AxisHeapCls v sobě fyzicky uchovává data materiálové osy S a kapacitní úkoly, a vztahy mezi nimi.
	/// </summary>
	public partial class AxisHeapCls : PlanningComponentCls, IDisposable
	{
		#region ČTENÍ DAT PLÁNU Z DATABÁZE
		/// <summary>
		/// Metoda zajistí načtení svých plánovacích dat z databáze.
		/// Nenačítají se číselníky ani definice, protože v této době (DataLoad()) jsou již číselníky načteny 
		/// (metoda DataPrepare() již  proběhla).
		/// Neprovádí se DataClear, protože to proběhlo centrálně před DataLoad (viz PlanningDataCls.DataLoad()).
		/// Načítají se záznamy osy S, kapacitní úkoly, vztahy, a internálie kapacitních úkolů (průchody, časy, jednotky).
		/// Data se navazují do stavů kapacit (vytváří se spojení Task.Unit «=» Desk.Job)
		/// </summary>
		internal override void DataLoad()
		{
			// Technika: připravím TEMP tabulky, které obsahují čísla nonsubjektů záznamu S a C, které budeme načítat (filtr podle verze plánu).
			// Pak načteme jednotlivé tabulky.
			// Nakonec smažu TEMP tabulky.

			try
			{
				this._DataLoadTempPrepare();

				this._WorkLoadIndex = new Dictionary<GID, GID>();

				this._DataLoadAxisS();
				this._DataLoadTaskC();
				this._DataLoadTaskI();
				this._DataLoadRelation();
				this._DataLoadTaskToAxis();
				this._DataLoadAxisFinalize();
			}
			finally
			{
				this._DataLoadTempDrop();
			}
		}
		/// <summary>
		/// Ukončí režim vstupu dat z databáze.
		/// Uvolní pracovní dočasná data, načtená do paměti v průběhu metody DataLoad().
		/// Tato data slouží k umožnění křížového dohledání záznamů i z jiných modulů.
		/// </summary>
		internal void DataLoadEnd()
		{
			this._WorkLoadIndex = null;
		}
		#region TEMP TABLE PREPARE and DROP
		/// <summary>
		/// V databázi vytvoří TEMP tabulky #plan_record_s a #plan_record_c, obsahující čísla nonsubjektu S a C pro záznamy, které budeme načítat.
		/// TEMP tabulky slouží pro subselecty při načítání dat z dalších tabulek.
		/// </summary>
		private void _DataLoadTempPrepare()
		{
			string sql = @"-- Create temp tables for load plan data of version @cv:
DECLARE @cv int;
SET @cv = {{PlanCVersion}};

IF EXISTS (select * from tempdb.sys.tables where left(name,15) = '#plan_record_s_')
	DROP TABLE #plan_record_s

CREATE TABLE #plan_record_s (cislo_nonsubjektu int not null)
CREATE INDEX #plan_record_s_idx1 ON #plan_record_s (cislo_nonsubjektu)

INSERT INTO #plan_record_s SELECT cislo_nonsubjektu FROM lcs.plan_unit_s_axis WHERE ISNULL(plan_c_version,0) = @cv

IF EXISTS (select * from tempdb.sys.tables where left(name,15) = '#plan_record_c_')
	DROP TABLE #plan_record_c

CREATE TABLE #plan_record_c (cislo_nonsubjektu int not null)
CREATE INDEX #plan_record_c_idx1 ON #plan_record_c (cislo_nonsubjektu)

INSERT INTO #plan_record_c SELECT cislo_nonsubjektu FROM lcs.plan_unit_c_task WHERE ISNULL(plan_c_version,0) = @cv

";
            sql = sql.Replace("{{PlanCVersion}}", this.ParentPlanning.PlanningParameters.PlanCVersion.ToString());

            Db_Layer.ExecuteNonQuery(sql, 480);              // 300 => 480  (5 min => 8 min)
		}
		/// <summary>
		/// Smaže TEMP tabulky z databáze.
		/// Neruší ale data pracovní načtená do paměti.
		/// </summary>
		private void _DataLoadTempDrop()
		{
			string sql = @"-- Drop temp tables for plan data load:
IF EXISTS (select * from tempdb.sys.tables where left(name,15) = '#plan_record_s_')
	DROP TABLE #plan_record_s

IF EXISTS (select * from tempdb.sys.tables where left(name,15) = '#plan_record_c_')
	DROP TABLE #plan_record_c
";
            Db_Layer.ExecuteNonQuery(sql);
		}
		#endregion
        #region LOAD AXIS S and internal data
        /// <summary>
		/// Načte data plánovací osy S, uloží je do dat osy a do materiálových dat
		/// </summary>
		private void _DataLoadAxisS()
		{
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeAxis, "Load.Axis", Constants.TraceKeyWordAplScheduler))
            {
                // scope.SuppressInnerTrace(null, null, "SQL");

                // Po dobu načítání dat na osu S pozastavíme třídění skladových karet:
                this.ParentPlanning.MaterialData.SuspendSorting = true;          // Pro tohle tam ten příznak existuje!

                string sql = PlanUnitSAxisCls.SqlSelect + " WHERE " + PlanUnitSAxisCls.SqlRecord + " in (select cislo_nonsubjektu from #plan_record_s)";
                List<PlanUnitSAxisCls> axisList = Db_Layer.GetList<PlanUnitSAxisCls>(sql);
                foreach (PlanUnitSAxisCls record in axisList)
                {
                    MaterialPlanAxisItemCls axisItem = MaterialPlanAxisItemCls.LoadFromDb(record);
                    this.ParentPlanning.MaterialData.FillMaterialAxisFromAX(ref axisItem);
                    GID sourceGId = this._DataLoadAxisGetSourceGId(axisItem);    // ze záznamu odvodí jeho SourceGID = odkaz na záznam "Způsobitel", pokud je o reálný vstup na osu z tohoto záznamu
                    this.ParentPlanning.AddPlanAxisSRecord(axisItem, sourceGId); // přidá záznam na osu i na skladovou kartu
                    this._WorkLoadIndex.Add(record.GId, axisItem.AxisGID);       // přidám do dočasného indexu klíč záznamu osy S z databáze a odpovídající ID
                    // V tuto chvíli ještě nelze určit, zda záznam osy S bude zobrazen v grafu Gantt, 
                    //  protože nemáme k dispozici vztahy (metoda _DataLoadRelation() poběží až za chvíli),
                    //  a bez vztahů nelze určit, zda záznam osy S je určen do hlavního řádku gantu 
                    //  (tam patří mj. návrhy na příjem, které mají množství větší, než je třeba pro navázané požadavky).
                }
                axisList.Clear();
                axisList = null;

                this.ParentPlanning.MaterialData.SuspendSorting = false;         // Od teď se můžou data na skladových kartách třídit. Fyzické setřídění se provede později (v MaterialData.DataLoadEnd())

                scope.User = new string[] { "Axis.Count = " + this._AxisS.Count.ToString() };
            }
		}
        /// <summary>
        /// Ze záznamu odvodí jeho SourceGID = odkaz na záznam "Způsobitel", pokud je o reálný vstup na osu z tohoto záznamu.
        /// Pracuje na základě hodnoty ChangeType.
        /// Tahle metoda by mohla být přímo ve třídě MaterialPlanAxisItemCls jako nějaká její property, ale logicky to tam nepatří - klíč SourceGId je do osy vepisovaný, osa jej nemá generovat !
        /// </summary>
        /// <param name="axisItem"></param>
        /// <returns></returns>
        private GID _DataLoadAxisGetSourceGId(MaterialPlanAxisItemCls axisItem)
        {
            int authorSubj = axisItem.AuthorSubj;
            int authorObj = axisItem.AuthorObj;
            switch (axisItem.ChangeType)
            {
                case PlanSAxisChangeType.IncrementByRealProductOrder:
                    return new GID(ProductOrderCls.ClassNr, authorSubj);
                case PlanSAxisChangeType.IncrementByRealSupplierOrder:
                    return new GID(Constants.ClassNumberObecnaObjednavka, -authorObj);
                case PlanSAxisChangeType.DecrementByRealEnquiry:
                    return new GID(Constants.ClassNumberObecnaPoptavka, -authorObj);
            }
            return GID.Empty;
        }
		/// <summary>
		/// Finalizuje načítání dat osy S po načtení všech záznamu (osa S, vztahy, tasky).
		/// Pro záznamy osy S, které patří do grafu Gantt, založí vztahy typu PlanningItemRelationType.AxisOutput, 
		/// na jejichž základě se plní graf Gantt.
		/// Tato metoda smí být spuštěna až po načtení dat osy S (po metodě _DataLoadAxisS())
		/// a po načtení vztahů (metoda _DataLoadRelation()), jinak nemá dostatek podkladů pro rozhodování.
		/// </summary>
		private void _DataLoadAxisFinalize()
		{
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeAxis, "Load.Axis.Finalize", Constants.TraceKeyWordAplScheduler))
            {
                // Vytvořím vztahy typu AxisOutput:
                this._MapFinalRelationsAxisOutput(false);
                // Do vztahů AxisToTask vygeneruji transferové sady, pokud cílový Task podporuje členění na transportní dávky:
                this._CreateTransferSetInRelationsAxisToTask();

                scope.User = new string[] { "Relation.Count = " + this._RelationDict.Count.ToString() };
            }
		}
		#endregion
		#region LOAD TASK C and internal data
		/// <summary>
		/// Načte data kapacitních úkolů
		/// </summary>
		private void _DataLoadTaskC()
		{
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeAxis, "Load.Task", Constants.TraceKeyWordAplScheduler))
            {
                // scope.SuppressInnerTrace(null, null, "SQL");

                string sql = PlanUnitCTaskCls.SqlSelect + " WHERE " + PlanUnitCTaskCls.SqlRecord + " in (select cislo_nonsubjektu from #plan_record_c)";
                List<PlanUnitCTaskCls> taskList = Db_Layer.GetList<PlanUnitCTaskCls>(sql);
                foreach (PlanUnitCTaskCls record in taskList)
                {
                    MaterialPlanAxisItemCls axisItem = this._DataLoadWorkIndexFindAxis(record.PlanUnitSAxis);
                    if (axisItem == null) continue;
                    CapacityPlanWorkItemCls taskItem = CapacityPlanWorkItemCls.LoadFromDb(record, axisItem);
                    if (taskItem == null) continue;

                    // Poznámka k fixovaným taskům (pouze operace VP, nikoli fixované návrhy - to neděláme):
                    // Jejich data přežijí přeplánování díky speciálnímu úložišti na třídě Operace VP, viz plánovací proces.

                    // Režim plánování tasku získám z odpovídajícího záznamu (operace VP / STPV):
                    taskItem.MfrOpPlanning = this._DataLoadTaskCFindPlanning(taskItem);

                    this.AddPlanTaskCRecord(taskItem);

                    this._WorkLoadIndex.Add(record.GId, taskItem.TaskGID);           // přidám do dočasného indexu klíč C tasku z databáze a odpovídající ID
                    this._WorkLoadIndex.Add(taskItem.TaskGID, axisItem.AxisGID);     // Index nahoru ve struktuře
                }
                taskList.Clear();
                taskList = null;

                scope.User = new string[] { "Task.Count = " + this._TaskC.Count.ToString() };
            }
		}
        /// <summary>
        /// Metoda najde a vrátí režim plánování pro daný task
        /// </summary>
        /// <param name="taskItem"></param>
        /// <returns></returns>
        private MfrOperationPlanning _DataLoadTaskCFindPlanning(CapacityPlanWorkItemCls taskItem)
        {
            ITechnologyLibrary tLibrary = this.ParentPlanning.GetTLibrary(taskItem.DocumentType);                     // Vrátí knihovnu T dokumentací pro STPV nebo pro VP, podle typu dokumentu
            if (tLibrary == null) return MfrOperationPlanning.PlanByProcess;
            IOperationData tOperation = tLibrary.GetOperationItem(taskItem.OperationKey, NotFindMode.GetFromDbNull);  // Vrátí data operace, nebo null
            if (tOperation == null) return MfrOperationPlanning.PlanByProcess;
            return tOperation.MfrOpPlanning;
        }
        /// <summary>
		/// Metoda načte interní data do tasků (Pass - Time - Unit),
		/// a zajistí provázání Unit do kapacitního systému (jako Job).
		/// </summary>
        private void _DataLoadTaskI()
        {
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeAxis, "Load.Task.Internals", Constants.TraceKeyWordAplScheduler))
            {
                // scope.SuppressInnerTrace(null, null, "SQL");

                string sql;

                // Pass:
                sql = PlanUnitCPassCls.SqlSelect + " WHERE plan_unit_c_task in (select cislo_nonsubjektu from #plan_record_c)";
                List<PlanUnitCPassCls> passList = Db_Layer.GetList<PlanUnitCPassCls>(sql);
                foreach (PlanUnitCPassCls record in passList)
                {
                    CapacityPlanWorkItemCls taskItem = this._DataLoadWorkIndexFindTask(record.PlanUnitCTask);
                    if (taskItem == null) continue;                          // Null = nějaký problém
                    WorkPassCls passItem = WorkPassCls.LoadFromDb(record, taskItem);
                    if (passItem == null) continue;

                    taskItem.DataLoadAddWorkPass(passItem);

                    this._WorkLoadIndex.Add(record.GId, passItem.PassGID);   // přidám do dočasného indexu klíč C průchodu z databáze a odpovídající ID
                    this._WorkLoadIndex.Add(passItem.PassGID, taskItem.TaskGID);   // Křížový index: GID průchodu Pass ukazuje na GID jeho tasku
                }
                int passCnt = passList.Count;
                passList.Clear();
                passList = null;

                // Time:
                sql = PlanUnitCTimeCls.SqlSelect + @" WHERE plan_unit_c_pass in (select cislo_nonsubjektu from lcs.plan_unit_c_pass p where p.plan_unit_c_task in (select cislo_nonsubjektu from #plan_record_c))";
                List<PlanUnitCTimeCls> timeList = Db_Layer.GetList<PlanUnitCTimeCls>(sql);
                foreach (PlanUnitCTimeCls record in timeList)
                {
                    WorkPassCls passItem = this._DataLoadWorkIndexFindPass(record.PlanUnitCPass);
                    if (passItem == null) continue;                         // Null = nějaký problém
                    WorkTimeCls timeItem = WorkTimeCls.LoadFromDb(record, passItem);

                    passItem.DataLoadAddWorkTime(timeItem);

                    this._WorkLoadIndex.Add(record.GId, timeItem.TimeGID);   // přidám do dočasného indexu klíč času z databáze a odpovídající ID
                    this._WorkLoadIndex.Add(timeItem.TimeGID, passItem.PassGID);   // Křížový index: GID času Time ukazuje na GID jeho průchodu Pass
                }
                int timeCnt = timeList.Count;
                timeList.Clear();
                timeList = null;

                // Unit:
                sql = PlanUnitCUnitCls.SqlSelect + @" WHERE plan_unit_c_time in (select cislo_nonsubjektu from lcs.plan_unit_c_time t where t.plan_unit_c_pass in (select cislo_nonsubjektu from lcs.plan_unit_c_pass p where p.plan_unit_c_task  in (select cislo_nonsubjektu from #plan_record_c)))";
                List<PlanUnitCUnitCls> unitList = Db_Layer.GetList<PlanUnitCUnitCls>(sql);
                foreach (PlanUnitCUnitCls record in unitList)
                {
                    WorkTimeCls timeItem = this._DataLoadWorkIndexFindTime(record.PlanUnitCTime);
                    if (timeItem == null) continue;                         // Null = nějaký problém

                    // Tady už musím najít kapacitní linku (Desk), která je zdrojem kapacity pro tento úkol:
                    CapacityDeskCls deskItem = this.ParentPlanning.CapacityData.DataLoadWorkFindDesk(record.PlanUnitCDesk);
                    if (deskItem == null) continue;

                    WorkUnitCls unitItem = WorkUnitCls.LoadFromDb(record, timeItem, deskItem);
                    if (unitItem == null) continue;

                    timeItem.AddWorkUnit(unitItem);

                    // Tady propojím WorkUnit (součást Tasku) a WorkJob (součást stavu kapacit - Desk):
                    deskItem.DataLoadAddWorkUnit(unitItem);

                    // Rovněž uložím WorkUnit do this._WorkD = index všech WorkUnit v systému, pro jejich snadné dohledání (a skrz ně na jejich Parenty):
                    this.AddWorkUnitRecord(unitItem);

                    this._WorkLoadIndex.Add(record.GId, unitItem.WorkGID);   // přidám do dočasného indexu klíč jednotky z databáze a odpovídající ID
                    this._WorkLoadIndex.Add(unitItem.WorkGID, timeItem.TimeGID);   // Křížový index: GID jednotky Time ukazuje na GID jeho času Time
                }
                int unitCnt = unitList.Count;
                unitList.Clear();
                unitList = null;

                scope.User = new string[]
                { 
                    "Pass.Count = " + passCnt.ToString(),
                    "Time.Count = " + timeCnt.ToString(),
                    "Unit.Count = " + unitCnt.ToString()
                };
            }

            // Finalizovat vnitřní data objektů Task po načtení jejich internálií:
            this._DataLoadTaskCFinalize();
        }
		/// <summary>
		/// Finalizovat interní data v kapacitních úkolech po načtení dat z databáze
		/// </summary>
		private void _DataLoadTaskCFinalize()
		{
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeAxis, "Load.Task.Finalize", Constants.TraceKeyWordAplScheduler))
            {
                foreach (CapacityPlanWorkItemCls taskItem in this._TaskC.Values)
                {
                    this._DataLoadTaskCFillTOperation(taskItem);
                    taskItem.DataLoadFinalize();
                }
                scope.User = new string[] { "Task.Count = " + this._TaskC.Count.ToString() };
            }
		}
		/// <summary>
		/// Do daného tasku naplní technologická data o navázané operaci,
		/// tak aby bylo možno task interaktivně přeregistrovávat
		/// </summary>
		/// <param name="task">Task pro naplnění daty operace</param>
		private void _DataLoadTaskCFillTOperation(CapacityPlanWorkItemCls task)
		{
			IOperationData operationData = null;
			switch (task.DocumentType)
			{
				case TDocumentType.PlanApproved:
					operationData = TDocumentCls.CreateOperation(task.DocumentKey, task.OperationKey, task.QtyFinal, this.ParentPlanning.TpvData, NotFindMode.GetFromDbNull);
					break;
				case TDocumentType.Production:
					operationData = TDocumentCls.CreateOperation(task.DocumentKey, task.OperationKey, task.QtyFinal, this.ParentPlanning.ProductData, NotFindMode.GetFromDbNull);
					break;
			}

			if (operationData != null && operationData.UnitCount == 1M)
			{
				// Zjistí nejmenší poměr použití kapacitní jednotky typu Pracoviště:
				decimal qtyOne = task.DetectMinRealWorkplaceUseRatio();
				if (qtyOne < 1M)
					TDocumentCls.SetQtyOneToOperationParallel(operationData, qtyOne);
			}

			task.FillCSource(operationData);
		}
		#endregion
		#region LOAD RELATIONS
		/// <summary>
		/// Načte veškeré vztahy a konvertuje je do plánovacího procesu
		/// </summary>
		private void _DataLoadRelation()
		{
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeAxis, "Load.Relations", Constants.TraceKeyWordAplScheduler))
            {
                // scope.SuppressInnerTrace(null, null, "SQL");

                string sql;

                // Vztahová tabulka 1386: "Zajištění požadavku" (vztahy mezi záznamy na ose S: balancování, transfer):
                //    Při rekonstrukci dat z této tabulky probíhá rekonstrukce balancování zásob, 
                //    a tedy stejně jako v metodě StockCardCls.GetAxisBalanceRelations() 
                //    se dopočítává údaj MaterialPlanAxisItemCls.QtyConsumed = Sum(výdeje z tohoto příjmu):
                sql = PusAxisLinkCls.SqlSelect + " WHERE order_res in (select cislo_nonsubjektu from #plan_record_s) OR request in (select cislo_nonsubjektu from #plan_record_s)";
                List<PusAxisLinkCls> sLinkList = Db_Layer.GetList<PusAxisLinkCls>(sql);
                foreach (PusAxisLinkCls sLink in sLinkList)
                {
                    MaterialPlanAxisItemCls axisRes = _DataLoadWorkIndexFindAxis(sLink.OrderRes);     // Záznam osy Zajištění (naše strana Prev)
                    MaterialPlanAxisItemCls axisRqs = _DataLoadWorkIndexFindAxis(sLink.Request);      // Záznam osy Požadavek (naše strana Next)
                    if (axisRes != null && axisRqs != null && axisRes.StockUnit != axisRqs.StockUnit)
                        // Vztah mezi záznamy na dvou různých skladových kartách (převod mezi sklady):
                        this.AddItemRelation(PlanningItemRelationType.AxisTransfer, axisRes.AxisGID, axisRqs.AxisGID, sLink.Qty);
                    else if (axisRes != null && axisRqs != null)
                    {	// Vztah mezi dvěma existujícími záznamy na téže skladové kartě:
                        this.AddItemRelation(PlanningItemRelationType.AxisBalance, axisRes.AxisGID, axisRqs.AxisGID, sLink.Qty);
                        axisRes.QtyConsumed += sLink.Qty;
                    }
                    else if (axisRes == null && axisRqs != null)
                        // Vztah odnikud do požadavku = spotřebuje se výchozí zásoba ze stavu skladu:
                        this.AddItemRelation(PlanningItemRelationType.AxisBalance, GID.Empty, axisRqs.AxisGID, sLink.Qty);
                    else if (axisRes != null && axisRqs == null)
                    {	// Vztah ze zajištění nikam = výroba na sklad:
                        this.AddItemRelation(PlanningItemRelationType.AxisBalance, axisRes.AxisGID, GID.Empty, sLink.Qty);
                        // Vztah typu AxisOutput se vytváří až po načtení, voláním centrální metody AxisHeapCls._MapFinalRelationsAxisOutput()
                    }
                }
                int sLinkCnt = sLinkList.Count;
                sLinkList.Clear();
                sLinkList = null;

                // Vztahová tabulka 1395: "Vztahy kapacitních úkolů" (vztahy o sousedních operacích, a o návaznosti operace finální na operaci na vyšším dílci):
                sql = PlanCTaskLinkCls.SqlSelect + " WHERE parent_task in (select cislo_nonsubjektu from #plan_record_c) or child_task in (select cislo_nonsubjektu from #plan_record_c)";
                List<PlanCTaskLinkCls> cLinkList = Db_Layer.GetList<PlanCTaskLinkCls>(sql);
                foreach (PlanCTaskLinkCls cLink in cLinkList)
                {
                    // Prev je Child, a Next je Parent. Totéž se objevuje v metodě this._SaveDataRelTaskToTask() i v this._DataLoadRelation()
                    CapacityPlanWorkItemCls taskPrev = _DataLoadWorkIndexFindTask(cLink.ChildTask);    // Záznam operace Child (naše strana Prev)
                    CapacityPlanWorkItemCls taskNext = _DataLoadWorkIndexFindTask(cLink.ParentTask);   // Záznam operace Parent (naše strana Next)

                    if (taskPrev != null && taskNext != null)
                    {	// Obě strany musí existovat, zde nejsou jednostranné vztahy:
                        if (taskPrev.AxisID == taskNext.AxisID)
                            // Záznam osy S je shodný => jde o operace na stejném dílci, tedy TaskInner:
                            this.AddItemRelation(PlanningItemRelationType.TaskInner, taskPrev.TaskGID, taskNext.TaskGID, taskPrev.Parent.Qty);
                        else
                            // Záznam osy S je rozdílný => jde o operace na různých dílcích, jde o sekundární vztah (TaskTransfer) z poslední operace A přes sklad do operace vyššího dílce:
                            this.AddItemRelation(PlanningItemRelationType.TaskTransfer, taskPrev.TaskGID, taskNext.TaskGID, taskPrev.Parent.Qty);
                    }
                }
                int cLinkCnt = cLinkList.Count;
                cLinkList.Clear();
                cLinkList = null;


                // Vztahová tabulka 1366: "Kapacitní úkoly a S osa vztahy" (vztahy o výdeji z osy S jako komponenta do operace = task C):
                //   Pozor: současně obsahuje vztahy typu Příjem vedlejšího produktu z operace na osu S !
                sql = PlanUnitScRelCls.SqlSelect + " WHERE plan_unit_s_axis in (select cislo_nonsubjektu from #plan_record_s) OR plan_unit_c_task in (select cislo_nonsubjektu from #plan_record_c)";
                List<PlanUnitScRelCls> scRelList = Db_Layer.GetList<PlanUnitScRelCls>(sql);
                foreach (PlanUnitScRelCls scRel in scRelList)
                {
                    MaterialPlanAxisItemCls axisMat = _DataLoadWorkIndexFindAxis(scRel.PlanUnitSAxis);  // Záznam osy Materiál (naše strana Prev)
                    CapacityPlanWorkItemCls taskOpe = _DataLoadWorkIndexFindTask(scRel.PlanUnitCTask);  // Záznam tasku Operace (naše strana Next)
                    if (axisMat == null || taskOpe == null) continue;

                    // Podle toho, jaký druh pohybu je uveden na ose, vytvoříme odpovídající vztah:
                    PlanningItemRelationType relationType = PlanningComponentCls.AxisChangeTypeScToRelType(axisMat.ChangeType);
                    switch (relationType)
                    {
                        case PlanningItemRelationType.TaskToAxisFinal:                // Příjem finálního výrobku:
                        case PlanningItemRelationType.TaskToAxisByProductSuitable:    // Příjem vedlejšího produktu shodného:
                        case PlanningItemRelationType.TaskToAxisByProductDissonant:   // Příjem vedlejšího produktu neshodného:
                            this.AddItemRelation(PlanningItemRelationType.TaskToAxisFinal, taskOpe.TaskGID, axisMat.AxisGID, axisMat.Qty);
                            break;
                        case PlanningItemRelationType.AxisToTask:                     // Výdej z osy do komponenty:
                            this.AddItemRelation(PlanningItemRelationType.AxisToTask, axisMat.AxisGID, taskOpe.TaskGID, axisMat.Qty);
                            break;
                    }
                }
                int scRelCnt = scRelList.Count;
                scRelList.Clear();
                scRelList = null;

                scope.User = new string[]
                { 
                    "AxisRel.Count = " + sLinkCnt.ToString(),
                    "TaskRel.Count = " + cLinkCnt.ToString(),
                    "TaskAxisRelt.Count = " + scRelCnt.ToString()
                };
            }

			this._DataLoadRelFinish();

			// Načtením vztahů jsme načetli i sekundární vztahy typu PlanningItemRelationType.TaskTransfer (ukládají se do databáze).
			this.FinalMapCreated = true;
		}
		/// <summary>
		/// Doplní takové vztahy, které se do databáze neukládají.
		/// Zatím se ukládají všechny.
		/// </summary>
		private void _DataLoadRelFinish()
		{
		}
		#endregion
		#region VLOŽENÍ TASKŮ DO ZÁZNAMŮ OSY S
		/// <summary>
		/// Vloží do záznamů osy S seznam jejích tasků.
		/// Do odpovídajících záznamů osy, ve správném pořadí (jde o LinkedList axis.TaskListInternal)
		/// </summary>
		private void _DataLoadTaskToAxis()
		{
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeAxis, "Load.FillTaskToAxis", Constants.TraceKeyWordAplScheduler))
            {
                // scope.SuppressInnerTrace(null, null, "SQL");

                foreach (CapacityPlanWorkItemCls task in this._TaskC.Values)
                {
                    MaterialPlanAxisItemCls axisItem = task.Parent;
                    if (axisItem == null) continue;

                    if (axisItem.TaskListInternal == null)
                        axisItem.TaskListInternal = new LinkedList<CapacityPlanWorkItemCls>();

                    if (axisItem.TaskListInternal.Count == 0)
                        axisItem.TaskListInternal.AddFirst(task);
                    else
                    {
                        // Načtu GID všech tasků, které jsou v rámci jednoho dílce před zdejším taskem:
                        // Pořadí je ToHistory (je to jen volba jedna ze dvou, srovnatelně lze napsat kód se směrem ToFuture).
                        List<GID> prevTaskList = this.FindTaskGIdInnerList(task.TaskGID, TimeRange.TimeDirection.ToHistory);

                        // Zde zkusím najít prvek seznamu axisItem.TaskListInternal, za který bych měl přidat zdejší task:
                        LinkedListNode<CapacityPlanWorkItemCls> addAfter = null;
                        for (int g = 1; g < prevTaskList.Count; g++)       // Začínám indexem 1, protože na indexu 0 je GID výchozího tasku, To je vlastnost metody FindTaskGIdInnerList().
                        {
                            GID prevGid = prevTaskList[g];                 // GIDy postupně od operace (task - 1) až k tasku operace 00
                            addAfter = axisItem.TaskListInternal.FindByPredicate(t => t.TaskGID == prevGid);
                            if (addAfter != null)
                                break;
                        }

                        // Pokud jsem našel nejbližší předchozí task, který už je uložen v axisItem.TaskListInternal,
                        //  pak zdejší nový task přidám za něj, jinak nový task přidám na začátek:
                        if (addAfter != null)
                            axisItem.TaskListInternal.AddAfter(addAfter, task);
                        else
                            axisItem.TaskListInternal.AddFirst(task);
                    }
                }
            }
		}
		#endregion
		#region REGENEROVÁNÍ TRANSFEROVÝCH SAD DO VZTAHŮ AxisToTask
        /// <summary>
        /// Do všech patřičných vztahů typu AxisToTask 
		/// (pokud jejich cílem je Task, který podporuje transportní dávky, a výdejem je jedna osa S) 
		/// vytvoří transforové sady.
        /// </summary>
        private void _CreateTransferSetInRelationsAxisToTask()
        {
            List<PlanningItemRelationCls> relations = this.FindRelationTypeData(PlanningItemRelationType.AxisToTask);
            foreach (PlanningItemRelationCls relation in relations)
            {
				// Transfery sestavujeme jen tehdy, když do daného tasku najdeme pro jednu komponentu pouze jeden vztah.
				// Pokud bych jednu komponentu vydával na víc položkách, jde o výdej "ByBatch", a takový výdej nemá oddělené transfery.
				// Důvody viz PlanningDataCls._ScheduleTStructure():
				//  - Pokud se výdeje komponent provádí v režimu (MrpStructureTimingType.ByBatch), 
				//    pak do jedné operace jde více výdejek (více záznamů osy S se vztahem AxisTotask)
				//     (za každou transportní dávku je jeden výdej), a pak se na vztah (relation) nenavazuje transfer.
				//     => každý jeden výdej je jedním transferem, a nedělí se dále na nižší úrovně.
				//  - Pokud se výdej komponenty provede jedním výdejem z osy, pak se na vztah navazuje transfer.
				//     => jeden výdej může mít více transferů.
				// Zjistíme tedy, kolik výdejů z osy S jde do té operace, na kterou ukazuje aktuální vztah relation:
				GID taskGId = relation.ItemGIDNext;
				MaterialPlanAxisItemCls axis = this.FindAxisSItem(relation.ItemGIDPrev);     // Záznam osy S = výdej komponenty do operace
				if (axis == null) continue;
				CapacityPlanWorkItemCls task = this.FindTaskCItem(taskGId);
				if (task == null) continue;
				
                // Zjistíme, kolika "dávkami" se z různých záznamů osy S vydává jedna a tatáž komponenta do jedné operace:
				List<MaterialPlanAxisItemCls> axisOtherList = this.FindRelationPrevAxisSList(relation.ItemGIDNext, r => r.Relation == PlanningItemRelationType.AxisToTask);  // Záznamy osy S s výdejem do komponenty do naší operace
				int axisCount = axisOtherList.Count(ax => (ax.PlanUnitS == axis.PlanUnitS && ax.StockPlan == axis.StockPlan));      // Kolikrát je tam výdej naší komponenty?
				if (axisCount > 1)
					continue;

				// Objekt task.CapacityBatchTask je autoinicializační:
				//  při prvním pokusu o get se zrekonstruuje podle svých vlastních časů. Ty jsou nyní již v tasku načteny.
				CapacityBatchTaskCls capacityBatchTask = task.CapacityBatchTask;
                if (capacityBatchTask.ContainBatch)
                {   // Operace obsahuje transportní dávky, může tedy vygenerovat transfery komponenty:
					// Musíme tedy najít osu S, abychom znali hodnotu tStructure.QtyRequired a klíč komponenty,
					//   a musíme mít data o T-komponentě, abychom znali tStructure.QtyOne:
					IStructureData tStructure = this.ParentPlanning.GetTStructure(axis);
					if (tStructure != null)
					{	// Vytvořit transferové sady pro množství vydávaných komponent, pro jednotkové množství komponenty:
						task.CapacityBatchTask.GetTransferSet(axis.Qty, tStructure.QtyOne);
					}
                }
            }
        }
        #endregion
        #region LOAD WORK INDEX : data, hledání v indexech
        /// <summary>
		/// Pracovní index při načítání dat plánu z databáze do paměti.
		/// Klíčem je GID záznamu v databázi, hodnotou je klíč GID téhož záznamu v interní datové struktuře.
		/// Současně obsahuje klíče GID záznamů v paměti (jejichž číslo třídy je jiné, než číslo třídy dat z databáze),
		/// hodnotou pro tyto záznamy je klíč GID nadřízeného prvku v paměti (v hierarchii: Unit - Time - Pass - Task - Axis).
		/// Objekt je vytvořen na začátku metody DataLoad(), zůstává naplněn i po jejím skončení, 
		/// a uvolňuje se z paměti až vyvoláním metody DataLoadEnd().
		/// </summary>
		private Dictionary<GID, GID> _WorkLoadIndex;
		/// <summary>
		/// Metoda najde a vrátí plánovací záznam osy S (MaterialPlanAxisItemCls) 
		/// podle jeho databázového klíče (číslo nonsubjektu).
		/// </summary>
		/// <param name="planUnitSAxis">Klíč záznamu osy S</param>
		/// <returns></returns>
		private MaterialPlanAxisItemCls _DataLoadWorkIndexFindAxis(SqlInt32 planUnitSAxis)
		{
			if (planUnitSAxis.IsNull) return null;
			GID axisDbGId = new GID(PlanUnitSAxisCls.ClassNr, planUnitSAxis.Value);
			GID axisGID;
			// V indexu _WorkLoadIndex jsou uloženy GID vstupních záznamů z databáze, a jejich odpovídající paměťová GID:
			if (!_WorkLoadIndex.TryGetValue(axisDbGId, out axisGID)) return null;
			return FindAxisSItem(axisGID);
		}
		/// <summary>
		/// Metoda najde plánovací záznam kapacitního úkolu C (CapacityPlanWorkItemCls) podle jeho databázového klíče (číslo nonsubjektu).
		/// </summary>
		/// <param name="planUnitCTask">Klíč kapacitního úkolu</param>
		/// <returns></returns>
		private CapacityPlanWorkItemCls _DataLoadWorkIndexFindTask(SqlInt32 planUnitCTask)
		{
			if (planUnitCTask.IsNull) return null;
			GID taskDbGId = new GID(PlanUnitCTaskCls.ClassNr, planUnitCTask.Value);
			GID taskGID;
			// V indexu _WorkLoadIndex jsou uloženy GID vstupních záznamů z databáze, a jejich odpovídající paměťová GID:
			if (!_WorkLoadIndex.TryGetValue(taskDbGId, out taskGID)) return null;
			return FindTaskCItem(taskGID);
		}
		/// <summary>
		/// Metoda najde a vrátí data pracovního průchodu (WorkPassCls) 
		/// podle jeho databázového klíče (číslo nonsubjektu).
		/// </summary>
		/// <param name="planUnitCPass">Klíč paralelního průchodu</param>
		/// <returns></returns>
		private WorkPassCls _DataLoadWorkIndexFindPass(SqlInt32 planUnitCPass)
		{
			if (planUnitCPass.IsNull) return null;
			GID passDbGId = new GID(PlanUnitCPassCls.ClassNr, planUnitCPass.Value);
			GID passGID;
			// V indexu _WorkLoadIndex jsou uloženy GID vstupních záznamů z databáze, a jejich odpovídající paměťová GID:
			if (!_WorkLoadIndex.TryGetValue(passDbGId, out passGID)) return null;
			
			// Další hledání na základě čísla průchodu PassGID (tj. klíč paměťového objektu) je v separátní metodě:
			return _DataLoadWorkIndexFindPass(passGID);
		}
		/// <summary>
		/// Metoda najde a vrátí data pracovního průchodu (WorkPassCls) 
		/// podle jeho paměťového klíče (passGID).
		/// </summary>
		/// <param name="passGID">Číslo GID paralelního průchodu</param>
		/// <returns></returns>
		private WorkPassCls _DataLoadWorkIndexFindPass(GID passGID)
		{
			// V pracovním indexu jsou uloženy křížové vztahy mezi záznamy v paměti (na základě jejich GID):
			// Pro klíč PassGID by měl být uložen GID jeho Parenta = TaskGID:
			GID taskGID;
			if (!_WorkLoadIndex.TryGetValue(passGID, out taskGID)) return null;
			CapacityPlanWorkItemCls taskItem = FindTaskCItem(taskGID);
			if (taskItem == null) return null;

			// Vrátíme Pass, podle jeho čísla PassID = zajistí to Task:
			return taskItem.FindPassItem(passGID);
		}
		/// <summary>
		/// Metoda najde a vrátí data pracovního času (WorkTimeCls) 
		/// podle jeho databázového klíče (číslo nonsubjektu).
		/// </summary>
		/// <param name="planUnitCTime"></param>
		/// <returns></returns>
		private WorkTimeCls _DataLoadWorkIndexFindTime(SqlInt32 planUnitCTime)
		{
			if (planUnitCTime.IsNull) return null;
			GID timeDbGId = new GID(PlanUnitCTimeCls.ClassNr, planUnitCTime.Value);
			GID timeGID;
			// V indexu _WorkLoadIndex jsou uloženy GID vstupních záznamů z databáze, a jejich odpovídající paměťová GID:
			if (!_WorkLoadIndex.TryGetValue(timeDbGId, out timeGID)) return null;

			// Další hledání na základě čísla času TimeGID (tj. klíč paměťového objektu) je v separátní metodě:
			return _DataLoadWorkIndexFindTime(timeGID);
		}
		/// <summary>
		/// Metoda najde a vrátí data pracovního času (WorkTimeCls) 
		/// podle jeho paměťového klíče (timeGID).
		/// </summary>
		/// <param name="timeGID">Číslo GID pracovního času</param>
		/// <returns></returns>
		private WorkTimeCls _DataLoadWorkIndexFindTime(GID timeGID)
		{
			// V pracovním indexu jsou uloženy křížové vztahy mezi záznamy v paměti (na základě jejich GID):
			// Pro klíč TimeGID by měl být uložen GID jeho Parenta = PassGID:
			GID passGID;
			if (!_WorkLoadIndex.TryGetValue(timeGID, out passGID)) return null;

			// Podle klíče PassGID (paměťový) najdeme jeho data, metoda je o kousek výše:
			WorkPassCls passItem = _DataLoadWorkIndexFindPass(passGID);
			if (passItem == null) return null;

			// Dohledání objektu času nechám na objektu WorkPassCls, protože časy v sobě spravuje on:
			return passItem.FindWorkTime(timeGID);
		}
		#endregion
		#endregion
		#region UKLÁDÁNÍ DAT PLÁNU DO DATABÁZE
		/// <summary>
		/// Metoda zajistí uložení svých dat do databáze
		/// </summary>
		internal void SaveData()
		{
			// Před smazáním vlastních dat plánu zajistím smazání záznamů LINK:
			this._SaveDeleteOldRecords();
			this._SaveDataAxisS();
			this._SaveDataTaskC();
			this._SaveDataRelation();
		}
		/// <summary>
		/// Smaže všechny záznamy z databáze, které se týkají aktuální verze plánu, a které obhospodařuje tento modul.
		/// Jde tedy o osu S, kapacitní úkoly včetně veškerých internálií (WorkPass, WorkTime, WorkUnit), vztahy mezi záznamy.
		/// </summary>
		private void _SaveDeleteOldRecords()
		{
			// Součástí smazání dat NENÍ smazání LINK záznamů:
			// -- delete link records: trigger on lcs.subjekty smaže i případné záznamy z tabulky master a uda, případně i dynamické vztahy i položky, podle definice třídy subjektu:
			// DELETE FROM lcs.subjekty WHERE cislo_subjektu in (select distinct link from lcs.plan_unit_c_task where link is not null and isnull(plan_c_version,0) = @cv)
			//  protože záznamy nezakládá plánovací proces, ale uživatel.
			//  A uživatel teoreticky může navázat jako LINK záznam jakýkoli subjekt, třeba i roční závěrku :-) !
            string sql = GetSelectForDeleteAxisHeapData(this.ParentPlanning.PlanningParameters.PlanCVersion);

            // DAJ 0044791 2013-05-20: na velkých DB je příkaz k mazání příliš dlouhotrvající, a protože běží pod jedním TimeOutem, pak někdy skončí chybou Timeout.
            // Proto byla vytvořena metoda Db_Layer.ExecuteNonQueryPart(), která daný SQL příkaz rozdělí v řádcích -- Part: a -- Final:
            sql = sql.Replace("-- Part:", Db_Layer.SQL_PART_ITEM);
            sql = sql.Replace("-- Final:", Db_Layer.SQL_PART_FINAL);

            Db_Layer.ExecuteNonQueryPart(sql, 600);

			// Po provedení výmazu z databáze musím smazat čísla RecordNumber v záznamech v paměti,
			//  tím zajistím při jejich ukládání režim INSERT, a ne UPDATE.
			foreach (MaterialPlanAxisItemCls planAxis in this._AxisS.Values)
				planAxis.StoreEmptyRecordNumber();
			foreach (CapacityPlanWorkItemCls planTask in this._TaskC.Values)
				planTask.StoreEmptyRecordNumber();
			foreach (PlanningItemRelationCls relation in this._RelationDict.Values)
				relation.StoreEmptyRecordNumber();
		}
        /// <summary>
        /// Metoda vrátí SQL příkaz, který smaže veškerá data plánu dané verze.
        /// Identický příkaz se generuje ve stejnojmenné metodě v Noris.LCS.Manufacturing:
        /// Noris.LCS.Manufacturing.DeletePlanDataCwl._GetSelectForDeleteAxisHeapData at C:\inetpub\wwwroot\Noris43\Noris\App\Lcs\Manufacturing\DeletePlanDataCwl.cs
        /// </summary>
        /// <param name="planCVersion"></param>
        /// <returns></returns>
        internal static string GetSelectForDeleteAxisHeapData(Int32? planCVersion)
        {
            // Smazání se provede jedním SQL příkazem, který využije temp tabulku pro uložení čísel záznamů osy S 
            //   a poté kapacitních úkolů, které se budou mazat (na základě čísla verze plánu):
            string sql = @"-- Delete all axis and task records for plan version {{PlanCVersion}}

-- temp table :
IF EXISTS (select * from tempdb.sys.tables where left(name,14) = '#plan_records_')
	DROP TABLE #plan_records

CREATE TABLE #plan_records (cislo_nonsubjektu int not null)
CREATE INDEX #plan_records_idx1 ON #plan_records (cislo_nonsubjektu)

-- delete axis S:
INSERT INTO #plan_records SELECT cislo_nonsubjektu FROM lcs.plan_unit_s_axis WHERE ISNULL(plan_c_version,0) = {{PlanCVersion}}

-- Part:
DELETE FROM lcs.plan_unit_s_axis_rel WHERE pu_s_axis_request in (select cislo_nonsubjektu from #plan_records)
-- Part:
DELETE FROM lcs.pus_axis_link WHERE order_res in (select cislo_nonsubjektu from #plan_records)
-- Part:
DELETE FROM lcs.pus_axis_link WHERE request in (select cislo_nonsubjektu from #plan_records)
-- Part:
DELETE FROM lcs.mrp_protocol WHERE pus_axis in (select cislo_nonsubjektu from #plan_records)
-- Part:
DELETE FROM lcs.plan_unit_sc_rel WHERE plan_unit_s_axis in (select cislo_nonsubjektu from #plan_records)
-- Part:
DELETE FROM lcs.plan_unit_s_axis WHERE cislo_nonsubjektu in (select cislo_nonsubjektu from #plan_records)

-- Part:
DELETE FROM #plan_records

-- Part:
-- delete C tasks:
INSERT INTO #plan_records SELECT cislo_nonsubjektu FROM lcs.plan_unit_c_task WHERE ISNULL(plan_c_version,0) = {{PlanCVersion}}

-- Part:
DELETE FROM lcs.plan_unit_c_cl_rel WHERE plan_unit_c_task in (select cislo_nonsubjektu from #plan_records)
-- Part:
DELETE FROM lcs.plan_unit_sc_rel WHERE plan_unit_c_task in (select cislo_nonsubjektu from #plan_records)
-- Part:
DELETE FROM lcs.plan_c_task_link WHERE child_task in (select cislo_nonsubjektu from #plan_records)
-- Part:
DELETE FROM lcs.plan_c_task_link WHERE parent_task in (select cislo_nonsubjektu from #plan_records)

-- Part:
-- Jednotky práce plánu, které se přes vztah Jednotka - Čas - Průchod vážou na tasky, které budeme mazat:
DELETE FROM lcs.plan_unit_c_unit WHERE cislo_nonsubjektu IN (
  select u.cislo_nonsubjektu
  from lcs.plan_unit_c_unit u
   left outer join lcs.plan_unit_c_time m on m.cislo_nonsubjektu = u.plan_unit_c_time
   left outer join lcs.plan_unit_c_pass p on p.cislo_nonsubjektu = m.plan_unit_c_pass
   left outer join lcs.plan_unit_c_task t on t.cislo_nonsubjektu = p.plan_unit_c_task
  where t.cislo_nonsubjektu is null or isnull(t.plan_c_version,0) = {{PlanCVersion}})

-- Part:
-- Časy práce plánu, které se vážou k průchodům, které patří do tasků, které budeme mazat:
DELETE FROM lcs.plan_unit_c_time WHERE cislo_nonsubjektu IN (
  select m.cislo_nonsubjektu
  from lcs.plan_unit_c_time m
   left outer join lcs.plan_unit_c_pass p on p.cislo_nonsubjektu = m.plan_unit_c_pass
   left outer join lcs.plan_unit_c_task t on t.cislo_nonsubjektu = p.plan_unit_c_task
  where t.cislo_nonsubjektu is null or isnull(t.plan_c_version,0) = {{PlanCVersion}})

-- Part:
-- Paralelní průchody, které patří do tasků, které budeme mazat:
DELETE FROM lcs.plan_unit_c_pass WHERE cislo_nonsubjektu IN (
  select p.cislo_nonsubjektu
  from lcs.plan_unit_c_pass p
   left outer join lcs.plan_unit_c_task t on t.cislo_nonsubjektu = p.plan_unit_c_task
  where t.cislo_nonsubjektu is null or isnull(t.plan_c_version,0) = {{PlanCVersion}})

-- Part:
-- Kapacitní tasky, které jsou součástí dané verze plánu = které budeme mazat:
DELETE FROM lcs.plan_unit_c_task WHERE isnull(plan_c_version,0) = {{PlanCVersion}}

-- Final:
DROP TABLE #plan_records
";
            sql = sql.Replace("{{PlanCVersion}}", (planCVersion.HasValue ? planCVersion.Value.ToString() : "0"));

            return sql;
        }
		/// <summary>
		/// Zajistí ukládání dat AxisS do databáze.
		/// Zde se neukládá statický vztah na jiné záznamy téže třídy (parent / descendant), to řeší metoda ukládání vztahů.
		/// </summary>
		private void _SaveDataAxisS()
		{
            PlanningParametersCls planningParameters = this.ParentPlanning.PlanningParameters;
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeAxis, "Save.Axis", Constants.TraceKeyWordAplScheduler, "Axis.Count = " + this._AxisS.Count.ToString()))
            {
                scope.SuppressInnerTrace(null, null, "SQL");
                foreach (MaterialPlanAxisItemCls planAxis in this._AxisS.Values)
                {
                    // Dohledat data stavu skladu, protože na osu se ukládají i tamní informace:
                    StockCardCls stockCard = this.ParentPlanning.MaterialData.GetStockCardOne(planAxis.StockUnit);
                    planAxis.SaveRecord(stockCard, planningParameters);
                }
            }
		}
		/// <summary>
		/// Zajistí ukládání dat TaskC do databáze
		/// </summary>
		private void _SaveDataTaskC()
		{
            PlanningParametersCls planningParameters = this.ParentPlanning.PlanningParameters;
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeAxis, "Save.Task", Constants.TraceKeyWordAplScheduler, "Task.Count = " + this._TaskC.Count.ToString()))
            {
                scope.SuppressInnerTrace(null, null, "SQL");
                foreach (CapacityPlanWorkItemCls planTask in this._TaskC.Values)
                {
                    planTask.SaveRecord(planningParameters);
                }
            }
		}
		#region UKLÁDÁNÍ VZTAHŮ
		/// <summary>
		/// Uloží všechny primární vztahy do odpovídajících tabulek Green
		/// </summary>
		private void _SaveDataRelation()
		{
			// debugCheckDupliciteKey(PlanningItemRelationType.TaskToAxisFinal);

			// Technika: projdu všechny záznamy vztahů, vyberu jen určité typy k uložení,
			//  pro ně dohledám záznamy na obou stranách vztahu, 
			//  a pokud se tyto datové záznamy mají ukládat, a jsou uloženy, 
			//  pak uložím záznam o vztahu do odpovídající tabulky:

			// Nebudu ale procházet všechny vztahy, když máme index vztahů:
			//  jinými slovy: není tomu tak, že by volaná metoda _SaveDataRelationOne() 
			//  procházela všechny vztahy, a z nich filtrovala jen vztahy daného typu.
			//  Ta metoda si načte seznam vztahů daného typu díky indexu, který je nad vztahy postaven, 
			//  a který pro konkrétní typ vztahu obsahuje reference na všechny vztahy daného typu.
			//  Získání všech vztahů jednoho druhu tedy trvá cca 0.681 mikrosekundy.
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeAxis, "Save.Relation", Constants.TraceKeyWordAplScheduler, "Task.Count = " + this.Relations.Count.ToString()))
            {
                scope.SuppressInnerTrace(null, null, "SQL");
                _SaveDataRelationOne(PlanningItemRelationType.AxisBalance, this._SaveDataRelAxisToAxis);           // Převody materiálu na jedné ose
                _SaveDataRelationOne(PlanningItemRelationType.AxisTransfer, this._SaveDataRelAxisToAxis);          // Převody materiálu mezi sklady
                _SaveDataRelationOne(PlanningItemRelationType.AxisToTask, this._SaveDataRelAxisToTask);            // Výdej materiálu do operace
                _SaveDataRelationOne(PlanningItemRelationType.TaskToAxisByProductSuitable, this._SaveDataRelTaskToByProduct);    // Příjem vedlejšího produktu shodného z výroby na sklad
                _SaveDataRelationOne(PlanningItemRelationType.TaskToAxisByProductDissonant, this._SaveDataRelTaskToByProduct);   // Příjem vedlejšího produktu neshodného z výroby na sklad
                _SaveDataRelationOne(PlanningItemRelationType.TaskToAxisFinal, this._SaveDataRelTaskToAxisFinal);                // Příjem finálního dílce z výroby na sklad
                _SaveDataRelationOne(PlanningItemRelationType.TaskInner, this._SaveDataRelTaskToTask);             // Předání rozpracovanosti uvnitř dílce
                _SaveDataRelationOne(PlanningItemRelationType.TaskTransfer, this._SaveDataRelTaskToTask);          // Návazná operace na jiném dílci
            }
		}
		/// <summary>
		/// debug: testuje duplicitu vztahu daného typu
		/// </summary>
		/// <param name="relType"></param>
		private void debugCheckDupliciteKey(PlanningItemRelationType relType)
		{
			List<PlanningItemRelationCls> dupList = new List<PlanningItemRelationCls>();
			Hashtable dupHt = new Hashtable();
			List<PlanningItemRelationCls> relList = this.FindRelationTypeData(relType);
			for (int i = 0; i < relList.Count; i++)
			{
				PlanningItemRelationCls relI = relList[i];
				for (int j = i + 1; j < relList.Count; j++)
				{
					if (dupHt.Contains(j)) continue;

					PlanningItemRelationCls relJ = relList[j];
					if (relI.ItemGIDPrev.RecordNumber == relJ.ItemGIDPrev.RecordNumber &&
						relI.ItemGIDNext.RecordNumber == relJ.ItemGIDNext.RecordNumber)
					{
						dupList.Add(relI);
						dupList.Add(relJ);
						dupHt.Add(j, null);
					}
				}
			}
			int dupCnt = dupList.Count;
		}
		/// <summary>
		/// Metoda pro uložení dat o vztazích jednoho druhu.
		/// Vstupem je druh vztahu, záznamy tohoto druhu dohledá pomocí indexu. Neprocházím tedy všechny vztahy foreach (filter) !!!
		/// Dalším vstupem je metoda (delegát), která řeší ukládání vztahů tohoto druhu.
		/// </summary>
		/// <param name="relType">Typ vztahu, který budeme řešit</param>
		/// <param name="saveMethod">Delegát na metodu, která bude ukládat data vztahů daného typu</param>
		private void _SaveDataRelationOne(PlanningItemRelationType relType, Action<PlanningItemRelationCls> saveMethod)
		{
			List<PlanningItemRelationCls> relList = this.FindRelationTypeData(relType);
			foreach (PlanningItemRelationCls relItem in relList)
				saveMethod(relItem);
		}
		/// <summary>
		/// Sestaví a uloží jeden záznam typu "Zajištění požadavku", lcs.pus_axis_link
		/// </summary>
		/// <param name="relation">Data o vztahu</param>
		private void _SaveDataRelAxisToAxis(PlanningItemRelationCls relation)
		{
			try
			{
				int axisPrevRec = _SaveDataGetRecordAxis(relation.ItemGIDPrev);
				int axisNextRec = _SaveDataGetRecordAxis(relation.ItemGIDNext);
				if (axisPrevRec == 0 && axisNextRec == 0) return;          // Propojka mezi počáteční zásobou a koncovou nevyužitou zásobou = neukládám.

				// Vztahová tabulka 1386: "Zajištění požadavku" (vztahy mezi záznamy na ose S: balancování, transfer):
				using (PusAxisLinkCls record = new PusAxisLinkCls())
				{
					record.Request = axisNextRec;
					record.OrderRes = axisPrevRec;
					record.Qty = relation.LinkedQty;
					relation.SaveViaRecord(record);
				}
			}
			catch (Exception exc)
			{
				// Dočasně výjimku ignoruji:
				// throw;
			}
		}
		/// <summary>
		/// Sestaví a uloží jeden záznam typu "Výdej komponenty do operace", do tabulky lcs.plan_unit_sc_rel
		/// </summary>
		/// <param name="relation">Data o vztahu</param>
		private void _SaveDataRelAxisToTask(PlanningItemRelationCls relation)
		{
			// Postup: vztah by měl ukazovat stranou Prev na záznam osy S s výdejem komponenty, 
			//         a stranou Next na záznam tasku (operace), do něhož komponenta vstupuje.
			//         Najdeme osu i task, a na tasku najdeme jeho parent = cílová osa S.
			MaterialPlanAxisItemCls axisPrev = this.FindAxisSItem(relation.ItemGIDPrev);      // Výdej komponenty
			CapacityPlanWorkItemCls taskTarg = this.FindTaskCItem(relation.ItemGIDNext);      // Operace

			// Pokud se záznam na plánovací ose neplánuje (=neukládá), nelze uložit ani vztah:
			if (!axisPrev.PlanovatSklad || !axisPrev.PlanovatKkzm)
				return;

			// Vztahová tabulka 1366: "Kapacitní úkoly a S osa vztahy" (vztahy o výdeji z osy S jako komponenta do operace = task C):
			using (PlanUnitScRelCls record = new PlanUnitScRelCls())
			{
				record.PlanUnitSAxis = axisPrev.RecordNumber;
				record.PlanUnitCTask = taskTarg.RecordNumber;
				relation.SaveViaRecord(record);
			}

			// Uložení čísla cílové osy S do plan_unit_s_axis.parent provedu přes axisPrev:
			MaterialPlanAxisItemCls axisNext = taskTarg.Parent;                               // Příjem finálu z výroby
			axisPrev.SetParentRecordNumber(axisNext.RecordNumber);
			axisPrev.SaveParentRecord();
		}
		/// <summary>
		/// Sestaví a uloží jeden záznam typu "Příjem vedlejšího produktu z operace na osu S", do tabulky lcs.plan_unit_sc_rel
		/// </summary>
		/// <param name="relation">Data o vztahu</param>
		private void _SaveDataRelTaskToByProduct(PlanningItemRelationCls relation)
		{
			this._SaveDataRelTaskToAxis(relation);
		}
		/// <summary>
		/// Sestaví a uloží jeden záznam typu "Příjem finálu z poslední operace výroby na osu S"
		/// </summary>
		/// <param name="relation">Data o vztahu</param>
		private void _SaveDataRelTaskToAxisFinal(PlanningItemRelationCls relation)
		{
			this._SaveDataRelTaskToAxis(relation);
		}
		/// <summary>
		/// Sestaví a uloží jeden záznam typu "Příjem z tasku na osu S"
		/// </summary>
		/// <param name="relation">Data o vztahu</param>
		private void _SaveDataRelTaskToAxis(PlanningItemRelationCls relation)
		{
			// Postup: vztah by měl ukazovat stranou Prev na kapacitní úkol, z něhož příjem probíhá,
			//         a stranou Next na záznam osy S, do něhož vedlejší produkt vstupuje jako příjem.
			//         Najdeme task i osu, a vytvoříme vztah.
			CapacityPlanWorkItemCls taskPrev = this.FindTaskCItem(relation.ItemGIDPrev);      // Operace, kde vedlejší produkt vzniká
			MaterialPlanAxisItemCls axisNext = this.FindAxisSItem(relation.ItemGIDNext);      // Příjem vedlejšího produktu

			// Vztahová tabulka 1366: "Kapacitní úkoly a S osa vztahy" (vztahy o výdeji z osy S jako komponenta do operace = task C):
			using (PlanUnitScRelCls record = new PlanUnitScRelCls())
			{
				record.PlanUnitSAxis = axisNext.RecordNumber;
				record.PlanUnitCTask = taskPrev.RecordNumber;
				relation.SaveViaRecord(record);
			}
		}
		/// <summary>
		/// Sestaví a uloží jeden záznam typu "Předání z operace na operaci"
		/// </summary>
		/// <param name="relation">Data o vztahu</param>
		private void _SaveDataRelTaskToTask(PlanningItemRelationCls relation)
		{
			int taskPrevRec = _SaveDataGetRecordTask(relation.ItemGIDPrev);
			int taskNextRec = _SaveDataGetRecordTask(relation.ItemGIDNext);
			if (taskPrevRec == 0 && taskPrevRec == 0) return;          // Propojka mezi počáteční zásobou a koncovou nevyužitou zásobou = neukládám.

			// Vztahová tabulka 1395: "Vztahy kapacitních úkolů" (vztahy o sousedních operacích, a o návaznosti operace finální na operaci na vyšším dílci):
			using (PlanCTaskLinkCls record = new PlanCTaskLinkCls())
			{
				// Prev je Child, a Next je Parent. Totéž se objevuje v metodě this._SaveDataRelTaskToTask() i v this._DataLoadRelation()
				record.ChildTask = taskPrevRec;
				record.ParentTask = taskNextRec;
				relation.SaveViaRecord(record);
			}
		}
		/// <summary>
		/// Najde a vrátí číslo databázového záznamu osy S (RecordNumber)
		/// </summary>
		/// <param name="axisGId"></param>
		/// <returns></returns>
		private int _SaveDataGetRecordAxis(GID axisGId)
		{
			if (axisGId.RecordNumber == 0) return 0;
			MaterialPlanAxisItemCls axisData = this.FindAxisSItem(axisGId);
			if (axisData == null || !axisData.EnablePlanning) return 0;
			if (axisData.RecordNumber <= 0)
				Throw.SysError(MessageInfo.Get("Chyba postupu při ukládání dat, záznam osy S ještě není uložen, a už se ukládají vztahy."));
			return axisData.RecordNumber;
		}
		/// <summary>
		/// Najde a vrátí číslo databázového záznamu kapacitního úkolu (RecordNumber)
		/// </summary>
		/// <param name="taskGId">Pro tento task se hledá klíč (RecordNumber) pro ukládání vztahů na něj</param>
		/// <returns></returns>
		private int _SaveDataGetRecordTask(GID taskGId)
		{
			if (taskGId.RecordNumber == 0) return 0;
			CapacityPlanWorkItemCls taskData = this.FindTaskCItem(taskGId);
			if (taskData == null) return 0;
			if (taskData.RecordNumber <= 0)
				Throw.SysError(MessageInfo.Get("Chyba postupu při ukládání dat, kapacitní úkol ještě není uložen, a už se ukládají vztahy."));
			return taskData.RecordNumber;
		}
		#endregion
		#endregion
	}
	#endregion
}
